package ca.uvic.cs.cogz.filters;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import javax.swing.JInternalFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.WindowConstants;

import ca.uvic.cs.cogz.mapping.TemporaryMappingManager;

import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.Frame;
import edu.stanford.smi.protege.resource.Icons;
import edu.stanford.smi.protege.ui.ConfigureAction;
import edu.stanford.smi.protegex.owl.model.impl.DefaultOWLIndividual;
import edu.stanford.smi.protegex.prompt.PromptTab;
import edu.stanford.smi.protegex.prompt.actionLists.ActionArgs;
import edu.stanford.smi.protegex.prompt.explanation.ApproximateMatch;
import edu.stanford.smi.protegex.prompt.explanation.Explanation;
import edu.stanford.smi.protegex.prompt.explanation.IdenticalNames;
import edu.stanford.smi.protegex.prompt.explanation.SameReferencedClses;
import edu.stanford.smi.protegex.prompt.explanation.SameRole;
import edu.stanford.smi.protegex.prompt.explanation.SubclassOf;
import edu.stanford.smi.protegex.prompt.explanation.SuperclassOf;
import edu.stanford.smi.protegex.prompt.operation.Operation;
import edu.stanford.smi.protegex.prompt.plugin.SelectionListener;
import edu.stanford.smi.protegex.prompt.plugin.util.KnowledgeBaseFacade;
import edu.stanford.smi.protegex.prompt.plugin.util.SelectableContainer;
import edu.stanford.smi.protegex.prompt.plugin.util.mapping.MapPluginFacade;
import edu.stanford.smi.protegex.prompt.ui.TabComponent;

/**
 * Manages the filter operations on the candidate mapping list.
 * 
 * @author seanf
 */
public class FilterPromptAction extends ConfigureAction implements SelectionListener {
	private static final long serialVersionUID = 1L;

	/** filtering constants */
	private static final int NO_FILTER = -1;
	private static final int EXACT_NAME_FILTER = 0;
	private static final int SIMILAR_NAME_FILTER = 1;
	private static final int SAME_REFERENCE_CLS = 2;
	private static final int SAME_ROLE = 3;
	private static final int SUB_CLASS_OF = 4;
	private static final int SUPER_CLASS_OF = 5;
	private static final int TEMPORARY_MAPPING = 6;
	private static final int OTHER = 7;

	/** an array of the different types of filters the user can apply */
	private Filter[] filters;

	/** the current filter being used */
	private int currentFilter = NO_FILTER;

	/** the actual suggestions that exist within the current hierarchy */
	private ArrayList workingSuggestions;

	/** the actual suggestions that are visible */
	private ArrayList currentlyVisibleSuggestions;

	private Collection<Frame> sourceFrames = null;
	private Collection<Frame> targetFrames = null;

	private SelectableContainer suggestionContainer;

	/** components used in the hierarchy filter window */
	private JInternalFrame sourceWindow;
	private HierarchyFilter hierarchyFilterPane;

	/** filter menu items */
	private JRadioButtonMenuItem allMenuItem;
	private JRadioButtonMenuItem exactNameMenuItem;
	private JRadioButtonMenuItem similarMenuItem;
	private JRadioButtonMenuItem sameReferenceClsMenuItem;
	private JRadioButtonMenuItem sameRoleMenuItem;
	private JRadioButtonMenuItem subClassOfMenuItem;
	private JRadioButtonMenuItem superClassOfMenuItem;
	private JRadioButtonMenuItem temporaryMappingMenuItem;
	private JRadioButtonMenuItem otherMenuItem;

	private JMenuItem hierarchyMenuItem;

	/** array of our menu items, this is to make processing the group nicer */
	private JRadioButtonMenuItem[] menuItems;

	public FilterPromptAction(String title, TemporaryMappingManager temporaryMappingManager) {
		putValue(NAME, title);

		OtherMatchesFilter otherMatchesFilter = new OtherMatchesFilter();

		this.filters = new Filter[] { new ExactNameFilter(), new SimilarNameFilter(), new SameReferenceClsFilter(), new SameRoleFilter(), new SubClassOfFilter(), new SuperClassOfFilter(),
				new TemporaryMappingFilter(temporaryMappingManager), otherMatchesFilter };

		otherMatchesFilter.setFilters(filters);

		allMenuItem = new JRadioButtonMenuItem("All", true);
		allMenuItem.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				allMenuItemPressed();
			}
		});

		exactNameMenuItem = new JRadioButtonMenuItem("Exact names", false);
		exactNameMenuItem.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				exactNameMenuItemPressed();
			}
		});

		similarMenuItem = new JRadioButtonMenuItem("Similar names", false);
		similarMenuItem.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				similarNameMenuItemPressed();
			}
		});

		sameReferenceClsMenuItem = new JRadioButtonMenuItem("Same reference clses", false);
		sameReferenceClsMenuItem.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				sameReferenceClsMenuItemPressed();
			}
		});

		sameRoleMenuItem = new JRadioButtonMenuItem("Same role", false);
		sameRoleMenuItem.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				sameRoleMenuItemPressed();
			}
		});

		subClassOfMenuItem = new JRadioButtonMenuItem("Sub class of", false);
		subClassOfMenuItem.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				subClassOfMenuItemPressed();
			}
		});

		superClassOfMenuItem = new JRadioButtonMenuItem("Super class of", false);
		superClassOfMenuItem.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				superClassOfMenuItemPressed();
			}
		});

		temporaryMappingMenuItem = new JRadioButtonMenuItem("Temporary mappings", false);
		temporaryMappingMenuItem.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				temporaryMenuItemPressed();
			}
		});

		otherMenuItem = new JRadioButtonMenuItem("Other matches", false);
		otherMenuItem.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				otherMenuItemPressed();
			}
		});

		hierarchyMenuItem = new JMenuItem("Hierarchy");
		hierarchyMenuItem.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				hierarchyMenuItemPressed();
			}
		});

		menuItems = new JRadioButtonMenuItem[] { allMenuItem, exactNameMenuItem, similarMenuItem, sameReferenceClsMenuItem, sameRoleMenuItem, subClassOfMenuItem, superClassOfMenuItem,
				temporaryMappingMenuItem, otherMenuItem };

		suggestionContainer = MapPluginFacade.getSelectableContainer(MapPluginFacade.UI_UTILITY_SUGGESTION);
		workingSuggestions = (ArrayList) suggestionContainer.getList();
		sourceFrames = new ArrayList<Frame>();
		targetFrames = new ArrayList<Frame>();

		sourceFrames.add(KnowledgeBaseFacade.getMappingSourceKnowledgeBaseInMerging().getKnowledgeBase().getRootCls());
		targetFrames.add(KnowledgeBaseFacade.getMappingTargetKnowledgeBaseInMerging().getKnowledgeBase().getRootCls());
	}
	public Collection<Frame> getSourceFrames() {
		return sourceFrames;
	}

	public Collection<Frame> getTargetFrames() {
		return targetFrames;
	}

	public void loadPopupMenu(JPopupMenu menu) {
		for (int i = 0; i < menuItems.length; i++) {
			menu.add(menuItems[i]);
		}
		menu.add(hierarchyMenuItem);
	}

	private void updateList(ArrayList suggestions) {
		currentlyVisibleSuggestions = suggestions;

		SelectableContainer container = MapPluginFacade.getSelectableContainer(MapPluginFacade.UI_UTILITY_SUGGESTION);
		container.updateList(suggestions);
	}

	/**
	 * Checks if the class is within the filtered frames collection or a child of the filtered frames
	 * 
	 * @param frames1
	 *            The frames to filter on
	 * @param c1
	 *            The class we want to determine whether to filter or not
	 * @return True if we don't want to filter this class, otherwise false.
	 */
	private boolean isNotFiltered(Collection<Frame> frames1, Cls c1) {
		for (Iterator iter2 = frames1.iterator(); iter2.hasNext();) {
			edu.stanford.smi.protege.model.Frame f = (edu.stanford.smi.protege.model.Frame) iter2.next();
			if (c1.getFrameID() == f.getFrameID()) {
				return true;
			}
		}

		Collection c = c1.getSuperclasses();

		for (Iterator iter = c.iterator(); iter.hasNext();) {
			Cls parent = (Cls) iter.next();
			for (Iterator iter2 = frames1.iterator(); iter2.hasNext();) {
				edu.stanford.smi.protege.model.Frame f = (edu.stanford.smi.protege.model.Frame) iter2.next();
				if (parent.getFrameID() == f.getFrameID()) {
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Filters based on the frames selected in the hierarchy filter
	 * 
	 * @param frames1
	 *            The frames collection from the first ontology
	 * @param frames2
	 *            The frames collection from the second ontology
	 */
	public void filterOnFrames(Collection frames1, Collection frames2, boolean hideSourceWindow) {
		if (frames1 != null && frames2 != null) {
			ArrayList suggestions = new ArrayList();
			ArrayList allSuggestions = (ArrayList) KnowledgeBaseFacade.getToDoListInPriorityOrder();

			for (int i = 0; i < allSuggestions.size(); i++) {
				Operation operation = (Operation) allSuggestions.get(i);

				ActionArgs args = operation.getArgs();
				if (args != null) {
					Cls c1 = null, c2 = null;

					// convert the args to Cls objects if possible
					if (args.getArg(0) instanceof Cls && args.getArg(1) instanceof Cls) {
						c1 = (Cls) args.getArg(0);
						c2 = (Cls) args.getArg(1);
					} else if (args.getArg(0) instanceof DefaultOWLIndividual && args.getArg(1) instanceof DefaultOWLIndividual) {
						c1 = ((DefaultOWLIndividual) args.getArg(0)).getDirectType();
						c2 = ((DefaultOWLIndividual) args.getArg(1)).getDirectType();
					}

					// check that both classes are children of the selected filters
					if (c1 != null && c2 != null && isNotFiltered(frames1, c1) && isNotFiltered(frames2, c2)) {
						suggestions.add(operation);
					}
				}
			}

			workingSuggestions = suggestions;
			sourceFrames = frames1;
			targetFrames = frames2;

			// re-apply any existing filters
			if (currentFilter == NO_FILTER) {
				updateList(suggestions);
			} else {
				applyFilter(currentFilter);
			}
		}

		if (hideSourceWindow) {
			sourceWindow.setVisible(false);
		}
	}

	/**
	 * Displays the hierarchy filtering window.
	 */
	private void showHierarchyFilterWindow() {
		TabComponent tab = PromptTab.getTabComponent();

		// create the window if it has not been created before
		if (sourceWindow == null) {
			createHierarchyWindow(tab);
		}

		sourceWindow.setVisible(true);
		sourceWindow.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
		sourceWindow.pack();

		try {
			sourceWindow.setSelected(true);
		} catch (java.beans.PropertyVetoException e) {
		}
	}

	/**
	 * Create the hierarchy filter window.
	 * 
	 * @param tab
	 */
	private void createHierarchyWindow(TabComponent tab) {
		String title = "Filter on knowledge bases";

		sourceWindow = new JInternalFrame(title, true, true, false, true);
		sourceWindow.setFrameIcon(Icons.getLogoIcon());
		sourceWindow.setPreferredSize(new Dimension(tab.getWidth() / 2, tab.getHeight() * 2 / 3));
		sourceWindow.setSize(sourceWindow.getPreferredSize());

		hierarchyFilterPane = new HierarchyFilter(sourceWindow.getSize(), this);

		sourceWindow.setLocation(tab.getWidth() / 2, 50);
		sourceWindow.getContentPane().setLayout(new BorderLayout());
		sourceWindow.getContentPane().add(hierarchyFilterPane, BorderLayout.CENTER);

		tab.add(sourceWindow);
	}

	/**
	 * Applys the filter class based on the filterType parameter
	 * 
	 * @param filterType
	 *            Corresponds to our filter constants
	 */
	private void applyFilter(int filterType) {
		applyFilter(filters[filterType]);
		currentFilter = filterType;
	}

	/**
	 * Applyes the Filter object
	 * 
	 * @param filter
	 *            The filter we want to apply
	 */
	private void applyFilter(Filter filter) {
		refreshWorkingSuggestions();

		ArrayList suggestions = new ArrayList();
		for (int i = 0; i < workingSuggestions.size(); i++) {
			Operation operation = (Operation) workingSuggestions.get(i);

			for (Iterator iter = operation.getReason().iterator(); iter.hasNext();) {
				Explanation explanation = (Explanation) iter.next();
				if (!filter.isFiltered(explanation)) {
					suggestions.add(operation);
					break;
				}
			}
		}

		updateList(suggestions);
	}

	/**
	 * Refresh the working suggestions if our visible suggestions are out of sync with Prompt's
	 * 
	 * @return True if the working suggestions were refreshed
	 */
	private boolean refreshWorkingSuggestions() {
		ArrayList promptSuggestions = (ArrayList) suggestionContainer.getList();

		// check to see if our suggestions have become outdated
		if (!promptSuggestions.equals(currentlyVisibleSuggestions)) {
			workingSuggestions = promptSuggestions;

			return true;
		}

		return false;
	}

	/**
	 * Shows all suggestions.
	 */
	private void showAll() {
		currentFilter = NO_FILTER;
		updateList(workingSuggestions);
	}

	/**
	 * Turn off all the menu item selection buttons.
	 */
	private void resetMenuItems() {
		for (int i = 0; i < menuItems.length; i++) {
			menuItems[i].setSelected(false);
		}
	}

	private void allMenuItemPressed() {
		if (!allMenuItem.isSelected()) {
			resetMenuItems();
			showAll();
		}
	}

	private void exactNameMenuItemPressed() {
		if (!exactNameMenuItem.isSelected()) {
			resetMenuItems();
			applyFilter(FilterPromptAction.EXACT_NAME_FILTER);
		}
	}

	private void similarNameMenuItemPressed() {
		if (!similarMenuItem.isSelected()) {
			resetMenuItems();
			applyFilter(FilterPromptAction.SIMILAR_NAME_FILTER);
		}
	}

	private void sameReferenceClsMenuItemPressed() {
		if (!sameReferenceClsMenuItem.isSelected()) {
			resetMenuItems();
			applyFilter(FilterPromptAction.SAME_REFERENCE_CLS);
		}
	}

	private void sameRoleMenuItemPressed() {
		if (!sameRoleMenuItem.isSelected()) {
			resetMenuItems();
			applyFilter(FilterPromptAction.SAME_ROLE);
		}
	}

	private void subClassOfMenuItemPressed() {
		if (!subClassOfMenuItem.isSelected()) {
			resetMenuItems();
			applyFilter(FilterPromptAction.SUB_CLASS_OF);
		}
	}

	private void superClassOfMenuItemPressed() {
		if (!superClassOfMenuItem.isSelected()) {
			resetMenuItems();
			applyFilter(FilterPromptAction.SUPER_CLASS_OF);
		}
	}

	private void temporaryMenuItemPressed() {
		if (!temporaryMappingMenuItem.isSelected()) {
			resetMenuItems();
			applyFilter(FilterPromptAction.TEMPORARY_MAPPING);
		}
	}

	private void otherMenuItemPressed() {
		if (!otherMenuItem.isSelected()) {
			resetMenuItems();
			applyFilter(FilterPromptAction.OTHER);
		}
	}

	private void hierarchyMenuItemPressed() {
		showHierarchyFilterWindow();
	}

	public void selectionPerformed(Object selectedItem) {
		// if we need to refresh our working suggestions, re-apply our filter
		if (refreshWorkingSuggestions()) {
			if (sourceFrames != null && targetFrames != null) {
				filterOnFrames(sourceFrames, targetFrames, false);
			} else if (currentFilter != NO_FILTER) {
				applyFilter(currentFilter);
			}
		}
	}
}

//filter classes
interface Filter {
	public boolean isFiltered(Explanation explanation);
}

class ExactNameFilter implements Filter {
	public boolean isFiltered(Explanation explanation) {
		if (explanation instanceof IdenticalNames) {
			return false;
		} else { // compare the actual frame names
			ActionArgs args = explanation.getOperation().getArgs();
			if (args.size() > 1 && args.getArg(0) instanceof Frame && args.getArg(1) instanceof Frame) {
				Frame f1 = (Frame) args.getArg(0);
				Frame f2 = (Frame) args.getArg(1);

				if (f1.getName().equals(f2.getName())) {
					return false;
				}
			}
		}

		return true;
	}
}

class SimilarNameFilter implements Filter {
	public boolean isFiltered(Explanation explanation) {
		return !(explanation instanceof ApproximateMatch);
	}
}

class SameReferenceClsFilter implements Filter {
	public boolean isFiltered(Explanation explanation) {
		return !(explanation instanceof SameReferencedClses);
	}
}

class SameRoleFilter implements Filter {
	public boolean isFiltered(Explanation explanation) {
		return !(explanation instanceof SameRole);
	}
}

class SubClassOfFilter implements Filter {
	public boolean isFiltered(Explanation explanation) {
		return !(explanation instanceof SubclassOf);
	}
}

class SuperClassOfFilter implements Filter {
	public boolean isFiltered(Explanation explanation) {
		return !(explanation instanceof SuperclassOf);
	}
}

class TemporaryMappingFilter implements Filter {
	private TemporaryMappingManager temporaryMappingManager;

	public TemporaryMappingFilter(TemporaryMappingManager temporaryMappingManager) {
		this.temporaryMappingManager = temporaryMappingManager;
	}

	public boolean isFiltered(Explanation explanation) {
		return !temporaryMappingManager.isTemporaryOperation(explanation.getOperation());
	}
}

class OtherMatchesFilter implements Filter {
	private Filter[] filters;

	public void setFilters(Filter[] filters) {
		this.filters = filters;
	}

	public boolean isFiltered(Explanation explanation) {
		// try to apply every filter
		for (int i = 0; i < filters.length - 1; i++) {
			if (filters[i].isFiltered(explanation)) {
				return true;
			}
		}
		return false;
	}
}
